﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DG.Tweening;
using TEngine;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;


[DisallowMultipleComponent]
public class InfiniteScroll : ScrollRect
{
    
    public class PoolGameObject
    {
        public GameObject _go;
        public object userData;
    }
    private class ScrollSlot
    {
        // Pool obj of scroll item
        public PoolGameObject item;
        public string path;

        public Vector2 layoutPos;
        public Vector2 layoutSize;
    }

    public enum ItemSetSiblingType
    {
        None = 0,
        First = 1,
        Last = 2,
    }

    public ItemSetSiblingType setAsSiblingType = ItemSetSiblingType.None;

    public Action<int, GameObject> releaseHandle;
    public Action<int, GameObject> refreshHandle;
    public Func<int, Vector2> itemSizeHandle;
    public Func<int, string> itemPathHandle;
    public Func<int> itemCountHandle;
    public Action<Vector2> deltaOnScrollHandle;

    private Rect _refRect;

    private int _dataCount;

    private Vector2 _prevPosition;
    private Vector2 _curDelta;
    private Vector2 _startPos;

    private readonly List<ScrollSlot> _slotList = new List<ScrollSlot>();
    private readonly List<float> _slotLinePosList = new List<float>();

    private HashSet<string> _pools = new HashSet<string>();

    private bool asyncRefresh = false;
    private List<KeyValuePair<PoolGameObject, Action>> refreshList = new();

    // 1 = Vertical 0 = Horizontal
    public enum ItemLayoutType
    {
        Vertical = 1,
        Horizontal = 0,
    }


    // resource management
    public Vector2 defaultItemSize;
    public Vector2 spacing;
    public Vector4 padding;
    public bool autoItemCount;
    public int constraint;

    public ItemLayoutType layoutType = ItemLayoutType.Vertical;

    public enum VerticalItemAlignment
    {
        Left = 0,
        Middle = 1,
        Right = 2,
    }

    public enum HorizontalItemAlignment
    {
        Upper = 0,
        Middle = 1,
    }

    public VerticalItemAlignment verticalItemAlignment = VerticalItemAlignment.Left;
    public HorizontalItemAlignment horizontalItemAlignment = HorizontalItemAlignment.Upper;

    public float ScrollLength
    {
        get
        {
            Vector2 contentPos = content.anchoredPosition;
            return horizontal ? -contentPos.x : contentPos.y;
        }
    }

    public void SetReleaseFunc(Action<int, GameObject> func)
    {
        releaseHandle = func;
    }

    // 指定刷新方法的回调，每个item刷新时调用 | Specifies the callback of the refresh method, which is called when each item is refreshed
    public void SetRefreshFunc(Action<int, GameObject> func)
    {
        refreshHandle = func;
    }

    // 指定item尺寸的回调，每个item刷新时调用 | Callback specifying item size, called when each item is refreshed
    public void SetItemSizeFunc(Func<int, Vector2> func)
    {
        if (autoItemCount)
        {
            Debug.LogError("autoItemCount is true, SetItemSizeFunc will be disable");
        }
        itemSizeHandle = func;
    }

    // 指定生成的prefab路径，每个item刷新时调用 | Specify the generated prefab path, which is called when each item is refreshed
    public void SetSpawnPrefabPath(Func<int, string> func)
    {
        itemPathHandle = func;
    }

    // 指定列表元素个数的回调，列表刷新时调用 | Callback that specifies the number of list elements, called when the list is refreshed
    public void SetItemCountFunc(Func<int> func)
    {
        itemCountHandle = func;
    }

    private bool _initialized;

    public void RefreshData(bool async = true)
    {
        if (!_initialized)
            InitScrollView();

        asyncRefresh = async;
        RefreshContentLayout();
        asyncRefresh = false;
    }

    protected override void Start()
    {
#if UNITY_EDITOR
        if (!Application.isPlaying)
            return;
#endif
        base.Start();
        if (verticalScrollbar)
        {
            verticalScrollbar.onValueChanged.AddListener((value) =>
            {
                ChangeItemsInSight();
            });
        }
    }

    private void InitScrollView()
    {
        _initialized = true;

        // Scroll direction
        vertical = (int) layoutType == 1;
        horizontal = (int) layoutType == 0;

        content.pivot = Vector2.up;
        if (autoItemCount)
        {
            if (layoutType == ItemLayoutType.Vertical)
            {
                if (defaultItemSize.x <= 0)
                {
                    Debug.LogError("autoItemCount but defaultItemSize.x is 0, constraint set by default 1");
                    constraint = 1;
                }
                else
                {
                    var w = content.rect.width;
                    var count = 0;
                    while (w > defaultItemSize.x)
                    {
                        count++;
                        w -= defaultItemSize.x;
                        w -= spacing.x;
                    }
                    constraint = Mathf.Max(1, count);
                }
            }
            else
            {
                if (defaultItemSize.y <= 0)
                {
                    Debug.LogError("autoItemCount but defaultItemSize.y is 0, constraint set by default 1");
                    constraint = 1;
                }
                else
                {
                    var h = content.rect.height;
                    var count = 0;
                    while (h > defaultItemSize.y)
                    {
                        count++;
                        h -= defaultItemSize.y;
                        h -= spacing.y;
                    }
                    constraint = Mathf.Max(1, count);
                }
            }
        }
        else
        {
            constraint = Mathf.Max(1, constraint);
        }

        var anchoredPosition = content.anchoredPosition;
        _curDelta = anchoredPosition - _prevPosition;
        _prevPosition = anchoredPosition;
    }

    private void RefreshContentLayout()
    {
        _dataCount = itemCountHandle?.Invoke() ?? 0;

        // 1.清空槽位
        ClearSlotList();

        // 2.重新生成槽位
        GenerateSlotList();
        // for (int i = 0; i < _slotList.Count; i++)
        // {
        //     Debug.LogFormat("{0} | {1}", i, _slotList[i].layoutPos);
        // }
        // Debug.LogFormat("Content Size {0}", content.rect.size);
        // for (int i = 0; i < _slotLinePosList.Count; i++)
        // {
        //     Debug.LogFormat("{0} | {1}", i, _slotLinePosList[i]);
        // }

        // 3.重新摆放显示实例
        int beginIndex = GetFirstShownIndex();
        int endIndex = GetLastShownIndex();
        PutItemsInSight(beginIndex, endIndex);
    }

    private void ClearItemsInSight()
    {
        for (int i = _oldEndIndex; i >= _oldBeginIndex; i--)
        {
            if (i < 0 || i >= _slotList.Count)
                continue;

            RecycleOldItem(i);
        }
    }

    private void ClearSlotList()
    {
        // 先把显示实例拿出去
        refreshList.Clear();
        ClearItemsInSight();
        _slotList.Clear();
    }

    private Vector2 GetStartPos(int line = 0)
    {
        Vector2 slotPos = Vector2.zero;

        int startIndex = line * constraint;
        Vector2 slotSize = GetItemSize(startIndex);

        if (layoutType == ItemLayoutType.Horizontal)
        {
            if (horizontalItemAlignment == HorizontalItemAlignment.Upper)
            {
                slotPos = new Vector2(padding.x, -padding.y);
                // Debug.Log(slotPos);
            }
            else
            {
                var usedH = viewRect.rect.height - (slotSize.y * constraint + spacing.y * (constraint - 1));
                slotPos = new Vector2(padding.x, -usedH / 2);
            }
        }
        else
        {
            if (verticalItemAlignment == VerticalItemAlignment.Left)
            {
                slotPos = new Vector2(slotSize.x + padding.x, -padding.y);
            }
            else if (verticalItemAlignment == VerticalItemAlignment.Middle)
            {
                var usedW = viewRect.rect.width - (slotSize.x * constraint + spacing.x * (constraint - 1));
                slotPos = new Vector2(slotSize.x + usedW / 2, -padding.y);
            }
            else
            {
                slotPos = new Vector2(viewRect.rect.width - padding.z, -padding.y);
            }
        }

        return slotPos;
    }

    private void GenerateSlotList()
    {
        if (_dataCount <= 0)
            return;

        _startPos = GetStartPos();

        // Debug.Log("========================== StartPos: " + _startPos);

        Vector2 slotPos = _startPos;
        for (int i = 0; i < _dataCount; i++)
        {
            AddSlot(ref slotPos, i);
        }

        RefreshSlotLinePosList();
        ResizeContent(slotPos);
    }

    private void AddSlot(ref Vector2 slotPos, int index)
    {
        Vector2 slotSize = GetItemSize(index);
        MovePos(ref slotPos, slotSize, index);
        ScrollSlot slot = new ScrollSlot {layoutPos = slotPos, layoutSize = slotSize};
        _slotList.Add(slot);
    }

    private void ResizeContent(Vector2 lastSlotPos)
    {
        _refRect = viewRect.rect;

        Vector2 range = new Vector2(Mathf.Abs(lastSlotPos.x), Mathf.Abs(lastSlotPos.y));
        switch (layoutType)
        {
            case ItemLayoutType.Horizontal:
                range.y = _refRect.height;
                range.x += padding.z;
                // range.x -= spacing.x;
                break;
            case ItemLayoutType.Vertical:
                range.x = _refRect.width;
                range.y += padding.w;
                // range.y -= spacing.y;
                break;
        }

        // Debug.LogFormat("Content Size {0}", range);
        content.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, range.x);
        content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, range.y);
    }

    private int _oldBeginIndex = -1;
    private int _oldEndIndex = -1;

    private void PutItemsInSight(int beginIndex, int endIndex)
    {
        // Debug.LogFormat("{0} | {1}", beginIndex, endIndex);

        // 补全视野内的
        for (int i = beginIndex; i <= endIndex; i++)
        {
            if (i >= _slotList.Count)
                break;

            if (_slotList[i].item == null)
            {
                GetNewItem(i);
            }
        }

        _oldBeginIndex = beginIndex;
        _oldEndIndex = endIndex;
    }

    private void ChangeItemsInSight()
    {
        int beginIndex = GetFirstShownIndex();
        int endIndex = GetLastShownIndex();
        // Debug.LogFormat("{0} {1} | {2} {3}", beginIndex, endIndex, _oldBeginIndex, _oldEndIndex);

        if (_oldBeginIndex == beginIndex && _oldEndIndex == endIndex)
            return;

        // 删掉视野外的
        if (_oldBeginIndex >= 0 && _oldEndIndex >= 0)
        {
            if (_oldBeginIndex < beginIndex) // ↓↓↓
            {
                int bottomBorder = beginIndex < _oldEndIndex ? beginIndex - 1 : _oldEndIndex;
                for (int i = _oldBeginIndex; i <= bottomBorder; i++)
                {
                    // Debug.Log("------------------" + i);
                    RecycleOldItem(i);
                }
            }
            else // ↑↑↑
            {
                int topBorder = endIndex > _oldBeginIndex ? endIndex + 1 : _oldBeginIndex;
                for (int i = topBorder; i <= _oldEndIndex; i++)
                {
                    // Debug.Log("------------------" + i);
                    RecycleOldItem(i);
                }
            }
        }

        // 补全视野内的
        PutItemsInSight(beginIndex, endIndex);
    }

    private Vector2 GetItemSize(int index)
    {
        if (autoItemCount)
        {
            #if UNITY_EDITOR
            Debug.Log("autoItemCount is true, GetItemSize will return defaultItemSize");
            #endif
            return defaultItemSize;
        }
        
        if (index < 0 || index > _dataCount)
            return defaultItemSize;
        return itemSizeHandle?.Invoke(index) ?? defaultItemSize;
    }

    private void MovePos(ref Vector2 pos, Vector2 size, int index)
    {
        int curLineIndex = index % constraint;
        switch (layoutType)
        {
            case ItemLayoutType.Horizontal:

                pos.y -= size.y + spacing.y;
                if (curLineIndex == 0)
                {
                    int curLine = index / constraint;
                    pos.y = GetStartPos(curLine).y;
                    pos.x += size.x;
                    if (index > 0)
                        pos.x += spacing.x;
                }

                break;
            case ItemLayoutType.Vertical:
                pos.x += size.x + spacing.x;
                if (curLineIndex == 0)
                {
                    int curLine = index / constraint;
                    pos.x = GetStartPos(curLine).x;
                    pos.y -= size.y;
                    if (index > 0)
                        pos.y -= spacing.y;
                }

                break;
        }
    }

    private void RefreshSlotLinePosList()
    {
        int line = 0;
        for (int i = 0; i < _slotList.Count; i++)
        {
            int curLineIndex = i % constraint;
            if (curLineIndex != 0)
                continue;

            line = i / constraint;
            float linePos = Mathf.Abs(horizontal ? _slotList[i].layoutPos.x : _slotList[i].layoutPos.y);
            if (line < _slotLinePosList.Count)
                _slotLinePosList[line] = linePos;
            else
                _slotLinePosList.Add(linePos);
        }

        for (int i = _slotLinePosList.Count - 1; i > line; i--)
        {
            _slotLinePosList.RemoveAt(i);
        }
    }

    private static int BinarySearch(List<float> list, float target, int left, int right)
    {
        if (list.Count == 0)
            return 0;
        if (target <= list.First())
            return 0;
        if (target >= list.Last())
            return list.Count - 1;

        // 保护退出
        if (left > right)
            return -1;

        int mid = Mathf.Clamp((left + right) / 2, 1, list.Count - 1);
        if (list[mid - 1] <= target && list[mid] >= target)
            return mid;

        return list[mid] > target
            ? BinarySearch(list, target, left, mid - 1)
            : BinarySearch(list, target, mid + 1, right);
    }


    protected override void SetContentAnchoredPosition(Vector2 position)
    {
#if UNITY_EDITOR
        if (!Application.isPlaying)
            return;
#endif

        base.SetContentAnchoredPosition(position);
        Vector2 anchoredPosition = content.anchoredPosition;
        _curDelta = anchoredPosition - _prevPosition;
        _prevPosition = anchoredPosition;

        if (Mathf.Abs(horizontal ? _curDelta.x : _curDelta.y) < float.Epsilon)
            return;

        // Debug.Log("Changed " + position);

        ChangeItemsInSight();
        deltaOnScrollHandle?.Invoke(_curDelta);
    }

    void OnGetNewItem(PoolGameObject item, int index, string path)
    {
        if (item != null)
        {
            item._go.transform.DOKill();
            item._go.name = Path.GetFileNameWithoutExtension(path) + "_" + index.ToString();
            item._go.SetActive(true);
            var rect = item._go.GetComponent<RectTransform>();
            rect.anchorMax = Vector2.up;
            rect.anchorMin = Vector2.up;
            if (layoutType == ItemLayoutType.Horizontal)
            {
                rect.pivot = new Vector2(1, 1);
            }
            else
            {
                rect.pivot = Vector2.right;
            }

            item._go.transform.SetParent(content, false);
            if (setAsSiblingType == ItemSetSiblingType.First)
            {
                item._go.transform.SetAsFirstSibling();
            }
            else if (setAsSiblingType == ItemSetSiblingType.Last)
            {
                item._go.transform.SetAsLastSibling();
            }

            RefreshDataForItemAtIndex(item, index);
            _slotList[index].item = item;
            _slotList[index].path = path;
        }
    }

    private void GetNewItem(int index)
    {
        if (itemPathHandle == null)
            return;

        string path = itemPathHandle.Invoke(index);
        if (!PoolManager.Instance.HasPool(path))
        {
            _pools.Add(path);
        }

        PoolGameObject a = new PoolGameObject();
        a._go = PoolManager.Instance.GetGameObject(path);
        OnGetNewItem(a, index, path);
        
    }

    private void RecycleOldItem(int index)
    {
        var slot = _slotList[index];
        if (slot.item == null)
        {
            return;
        }
        
        RemoveFromRefreshList(slot.item);

        releaseHandle?.Invoke(index, slot.item._go);

        slot.item._go.transform.DOKill();
        slot.item._go.SetActive(false);
        PoolManager.Instance.PushGameObject(slot.item._go);
        slot.item = null;
        slot.path = null;
    }

    private void RemoveFromRefreshList(PoolGameObject item)
    {
        var find = refreshList.FindIndex((pair => pair.Key == item));
        if (find >= 0)
        {
            refreshList.RemoveAt(find);
        }
    }

    private void RefreshDataForItemAtIndex(PoolGameObject item, int index)
    {
        var tr = item._go.GetComponent<RectTransform>();
        tr.localScale = Vector3.one;
        tr.localPosition = _slotList[index].layoutPos;
        tr.sizeDelta = _slotList[index].layoutSize;

        if (asyncRefresh)
        {
            RemoveFromRefreshList(item);
            
            var _item = item;
            var _index = index;
            _item._go.SetActive(false);
            refreshList.Add(new KeyValuePair<PoolGameObject, Action>(item, () =>
            {
                _item._go.SetActive(true);
                refreshHandle?.Invoke(_index, _item._go);
            }));
        }
        else
        {
            item._go.SetActive(true);
            refreshHandle?.Invoke(index, item._go);
        }
    }

    public GameObject GetShownItemByIndex(int index)
    {
        if (index < 0 || index >= _slotList.Count || _slotList[index].item == null)
            return null;

        return _slotList[index].item._go;
    }

    public int GetShownIndexByItem(GameObject go)
    {
        for (int i = 0; i < _slotList.Count; i++)
        {
            if (_slotList[i].item != null && _slotList[i].item._go == go)
            {
                return i;
            }
        }

        return -1;
    }

    public Vector2 GetSlotPosByIndex(int index)
    {
        if (index < 0 || index >= _slotList.Count)
            return Vector2.zero;

        return _slotList[index].layoutPos;
    }

    private Tweener _tweener;

    public Tweener AnimateToRealPosition(Vector2 pos, float duration = 0, Action onComplete = null)
    {
        return AnimateMove(SetContentAnchoredPosition, pos, duration, onComplete);
    }

    public Tweener AnimateToRealNormalizedPosition(Vector2 normalizedPos, float duration = 0, Action onComplete = null)
    {
        return AnimateMove(SetContentAnchoredPosition, NormalizedPos2ContentPos(normalizedPos), duration, onComplete);
    }

    public Tweener AnimateToIndex(int index, float duration = 0, float offset = 0, Action onComplete = null)
    {
        var pervIndex = index - 1;
        if (_slotList.Count <= 0 || pervIndex < 0)
        {
            return AnimateToRealNormalizedPosition(layoutType == ItemLayoutType.Horizontal ? Vector2.zero : Vector2.one,
                duration, onComplete);
        }

        if (pervIndex >= _slotList.Count)
        {
            return AnimateToRealNormalizedPosition(layoutType == ItemLayoutType.Horizontal ? Vector2.one : Vector2.zero,
                duration, onComplete);
        }

        var pos = _slotList[pervIndex].layoutPos;
        float min, max;
        if (layoutType == ItemLayoutType.Horizontal)
        {
            min = NormalizedPos2ContentPos(Vector2.one).x;
            max = NormalizedPos2ContentPos(Vector2.zero).x;
            pos.x = Mathf.Clamp(-pos.x + offset, min, max);
        }
        else
        {
            min = NormalizedPos2ContentPos(Vector2.one).y;
            max = NormalizedPos2ContentPos(Vector2.zero).y;
            pos.y = Mathf.Clamp(-pos.y + offset, min, max);
        }

        return AnimateToRealPosition(pos, duration, onComplete);
    }

    public Vector3 NormalizedPos2ContentPos(Vector2 normalizedPos)
    {
        Vector3 localPosition = content.localPosition;
        for (int i = 0; i < 2; i++)
        {
            float len = Mathf.Max(0, content.rect.size[i] - viewport.rect.size[i]);
            float target = (layoutType == ItemLayoutType.Horizontal ? -normalizedPos[i] : 1 - normalizedPos[i]) * len;
            localPosition[i] = target;
        }

        return localPosition;
    }

    public bool IsAnimateMoving()
    {
        return _tweener != null;
    }

    private Tweener AnimateMove(Action<Vector2> moveFunc, Vector2 target, float duration, Action onComplete = null)
    {
        // if (vertical && content.rect.height < viewport.rect.height)
        //     return;
        // if (horizontal && content.rect.width < viewport.rect.width)
        //     return;

        if (_dataCount <= 0)
        {
            onComplete?.Invoke();
            return null;
        }

        velocity = Vector2.zero;
        _tweener?.Kill();
        if (duration <= 0)
        {
            moveFunc?.Invoke(target);
            onComplete?.Invoke();
            return null;
        }

        _tweener = DOTween.To(() => (Vector2) NormalizedPos2ContentPos(normalizedPosition),
                delta => { moveFunc?.Invoke(delta); }, target, duration)
            .OnKill(() => _tweener = null).OnComplete(() =>
            {
                _tweener = null;
                onComplete?.Invoke();
            });
        return _tweener;
    }

    public void InsertItem(int pos = -1, int addCount = 1)
    {
        int count = _dataCount;
        for (int i = 0; i < addCount; i++)
            _slotList.Add(new ScrollSlot());
        _dataCount = count + addCount;

        int insertPos = pos < 0 ? count : Mathf.Min(pos, count);
        RefreshSlotLayout(insertPos);
        RefreshShownItems();
    }

    public void RemoveItem(int pos = -1, int removeCount = 1)
    {
        int count = _dataCount;
        for (int i = 0; i < removeCount; i++)
        {
            int index = _slotList.Count - 1;
            RecycleOldItem(index);
            _slotList.RemoveAt(index);
        }

        _dataCount = count - removeCount;

        int removePos = pos < 0 ? _dataCount : Mathf.Min(pos, count);
        RefreshSlotLayout(removePos);
        RefreshShownItems();
    }
    
    public GameObject RemoveItemWithAnimation(int pos, bool needReturnRemovedItem = false, float otherItemDelayBeforeMove = 0, float otherItemMoveDuration = 0.3f, Ease ease = Ease.InSine, Action onAnimDone = null)
    {
        if (_dataCount <= 0)
        {
            return null;
        }
        
        if (pos < 0 || pos >= _slotList.Count)
        {
            return null;
        }

        if (_slotList[pos].item == null)
        {
            return null;
        }
        
        var sizeTemp = _slotList[pos].layoutSize;

        GameObject ret = null;
        if (needReturnRemovedItem)
        {
            var temp = _slotList[pos].item._go;
            ret = Instantiate(temp, temp.transform.parent);
            ret.transform.localRotation = temp.transform.localRotation;
            var rectTr = ret.transform.GetComponent<RectTransform>();
            if (layoutType == ItemLayoutType.Horizontal)
            {
                rectTr.pivot = new Vector2(0, 0.5f); // 1, 1  ---> 0, 0.5
            }
            else
            {
                rectTr.pivot = new Vector2(0.5f, 1); // 1, 0  ---> 0, 0.5
            }
            var offsetPivot = rectTr.pivot - temp.transform.GetComponent<RectTransform>().pivot;
            var offsetPos = new Vector3(offsetPivot.x * sizeTemp.x, offsetPivot.y * sizeTemp.y, 0);
            ret.transform.localPosition = temp.transform.localPosition + offsetPos;
        }

        RecycleOldItem(pos);
        
        _slotList.RemoveAt(pos);
        
        _dataCount = itemCountHandle?.Invoke() ?? 0;
        
        RefreshSlotLayout(pos);
        
        int newLastIndex = GetLastShownIndex();
        
        for (int i = pos; i <= newLastIndex; i++)
        {
            if (i >= _slotList.Count)
                continue;

            if (_slotList[i].item == null)
            {
                GetNewItem(i);
            }
        }
        
        int animCount = 0;
        
        var completeFunc = new TweenCallback(() =>
        {
            animCount--;
            
            if (animCount <= 0)
            {
                RefreshShownItems();
                onAnimDone?.Invoke();
                onAnimDone = null;
            }
        });
        
        for (int i = pos; i <= newLastIndex; i++)
        {
            if (i < 0 || i >= _slotList.Count)
            {
                continue;
            }
            
            if (_slotList[i].item == null)
            {
                continue;
            }
        
            animCount++;
        
            _slotList[i].item._go.transform.DOKill();
            
            var dstPos = _slotList[i].layoutPos;
            
            if ((layoutType == ItemLayoutType.Horizontal && constraint == 1) || (layoutType == ItemLayoutType.Vertical && constraint > 1))
            {
                _slotList[i].item._go.transform.localPosition = dstPos + Vector2.right * (sizeTemp.x + spacing.x);
                _slotList[i].item._go.transform.DOLocalMoveX(dstPos.x, otherItemMoveDuration).OnComplete(completeFunc).SetDelay(otherItemDelayBeforeMove).SetEase(ease);
            }
            else
            {
                _slotList[i].item._go.transform.localPosition = dstPos - Vector2.up * (sizeTemp.y + spacing.y);
                _slotList[i].item._go.transform.DOLocalMoveY(dstPos.y, otherItemMoveDuration).OnComplete(completeFunc).SetDelay(otherItemDelayBeforeMove).SetEase(ease);
            }
        }
        
        _oldEndIndex = newLastIndex;

        if (animCount == 0)
        {
            completeFunc.Invoke();
        }
        
        return ret;
    }
    
    public GameObject RemoveItemWithAutoScaleAnimation(int pos, float autoScaleTime = 0.1f, float otherItemDelayBeforeMove = 0, float otherItemMoveDuration = 0.2f, Ease ease = Ease.InSine, Action onAnimDone = null)
    {
        var ret = RemoveItemWithAnimation(pos, true, otherItemDelayBeforeMove, otherItemMoveDuration, ease, onAnimDone);
        if (ret)
        {
            var canvasGroup = ret.GetComponent<CanvasGroup>();
            if (canvasGroup == null)
            {
                canvasGroup = ret.AddComponent<CanvasGroup>();
            }
            
            canvasGroup.enabled = true;
            canvasGroup.interactable = true;
            canvasGroup.blocksRaycasts = false;
            
            if (layoutType == ItemLayoutType.Horizontal)
            {
                ret.transform.DOScaleX(0, autoScaleTime).OnComplete(() => { Destroy(ret); });
            }
            else
            {
                ret.transform.DOScaleY(0, autoScaleTime).OnComplete(() => { Destroy(ret); });
            }
        }
        return ret;
    }

    public GameObject RemoveItemWithAutoFadeAnimation(int pos, float autoFadeTime = 0.2f, float otherItemDelayBeforeMove = 0, float otherItemMoveDuration = 0.2f, Ease ease = Ease.InSine, Action onAnimDone = null)
    {
        var ret = RemoveItemWithAnimation(pos, true, otherItemDelayBeforeMove, otherItemMoveDuration, ease, onAnimDone);
        if (ret)
        {
            var canvasGroup = ret.GetComponent<CanvasGroup>();
            if (canvasGroup == null)
            {
                canvasGroup = ret.AddComponent<CanvasGroup>();
            }
            
            canvasGroup.enabled = true;
            canvasGroup.interactable = true;
            canvasGroup.blocksRaycasts = false;
            
            canvasGroup.DOFade(0, autoFadeTime).OnComplete(() =>
            {
                Destroy(ret);
            });
        }
        return ret;
    }
    
    public GameObject RemoveItemWithAutoScaleAndFadeAnimation(int pos, float autoScaleFadeTime = 0.2f, float otherItemDelayBeforeMove = 0, float otherItemMoveDuration = 0.3f, Ease ease = Ease.InSine, Action onAnimDone = null)
    {
        var ret = RemoveItemWithAnimation(pos, true, otherItemDelayBeforeMove, otherItemMoveDuration, ease, onAnimDone);
        if (ret)
        {
            if (layoutType == ItemLayoutType.Horizontal)
            {
                ret.transform.DOScaleX(0, autoScaleFadeTime).OnComplete(() => { Destroy(ret); });
            }
            else
            {
                ret.transform.DOScaleY(0, autoScaleFadeTime).OnComplete(() => { Destroy(ret); });
            }

            var canvasGroup = ret.GetComponent<CanvasGroup>();
            if (canvasGroup == null)
            {
                canvasGroup = ret.AddComponent<CanvasGroup>();
            }
            
            canvasGroup.enabled = true;
            canvasGroup.interactable = true;
            canvasGroup.blocksRaycasts = false;

            canvasGroup.DOFade(0, autoScaleFadeTime).OnComplete(() =>
            {
                Destroy(ret);
            });
        }
        return ret;
    }


    private void RefreshSlotLayout(int refreshFrom)
    {
        if (refreshFrom < 0)
            return;
        
        for (int i = _slotList.Count; i < _dataCount; i++)
        {
            Vector2 temp = new Vector2();
            AddSlot(ref temp, i);
        }

        Vector2 slotPos = refreshFrom == 0 ? _startPos : _slotList[refreshFrom - 1].layoutPos;
        for (int i = refreshFrom; i < _slotList.Count; i++)
        {
            Vector2 slotSize = GetItemSize(i);
            MovePos(ref slotPos, slotSize, i);
            _slotList[i].layoutPos = slotPos;
            _slotList[i].layoutSize = slotSize;
        }

        RefreshSlotLinePosList();
        ResizeContent(slotPos);
    }

    public void RefreshShownItems(bool async = false)
    {
        // 检查是否有尺寸变化
        int? refreshFrom = null;
        for (int i = _oldBeginIndex; i <= _oldEndIndex; i++)
        {
            if (i < 0 || i >= _slotList.Count)
                continue;

            if (_slotList[i].layoutSize != GetItemSize(i))
            {
                refreshFrom = i;
                break;
            }
        }

        // 如果有尺寸变化，先刷新layout
        if (refreshFrom.HasValue)
        {
            RefreshSlotLayout(refreshFrom.Value);
        }

        // 刷新视野中的item
        refreshList.Clear();
        ClearItemsInSight();

        int beginIndex = GetFirstShownIndex();
        int endIndex = GetLastShownIndex();

        asyncRefresh = async;
        PutItemsInSight(beginIndex, endIndex);
        asyncRefresh = false;
    }

    public void DeltaOnScroll(Action<Vector2> func)
    {
        deltaOnScrollHandle = func;
    }

    public int GetFirstShownIndex()
    {
        int beginLine = BinarySearch(_slotLinePosList, ScrollLength, 0, _slotLinePosList.Count - 1);
        return beginLine * constraint;
    }

    public int GetLastShownIndex()
    {
        Rect viewPortRect = viewport.rect;
        float endScrollLen = ScrollLength + (horizontal ? viewPortRect.width : viewPortRect.height);
        int endLine = BinarySearch(_slotLinePosList, endScrollLen, 0, _slotLinePosList.Count - 1);
        return Mathf.Min((endLine + 1) * constraint - 1, _slotList.Count - 1);
    }


    public void EmptyScroll()
    {
        var temp = itemCountHandle;
        itemCountHandle = null;
        RefreshData();
        itemCountHandle = temp;
    }


    protected override void OnDestroy()
    {
        // Clear pool   
        foreach (var pool in _pools)
        {
            PoolManager.Instance.ClearGameObject(pool);
        }
    }


    private bool _isDragging = false;
    private float _delayDisableT = -1;

    private Vector2 dragPos = Vector2.zero;
    public Action<int> endDraghHandle;

    public void SetEndDragFunc(Action<int> _endDraghHandle)
    {
        endDraghHandle = _endDraghHandle;
    }

    public override void OnBeginDrag(PointerEventData eventData)
    {
        if (content != null)
            dragPos = content.anchoredPosition;
        base.OnBeginDrag(eventData);
        _isDragging = true;
    }

    /// <summary>
    ///   <para>Handling for when the content has finished being dragged.</para>
    /// </summary>
    /// <param name="eventData"></param>
    public override void OnEndDrag(PointerEventData eventData)
    {
        base.OnEndDrag(eventData);
        _isDragging = false;
        _delayDisableT = 1f;

        if (content != null)
        {
            Vector2 vt = dragPos - content.anchoredPosition;
            int dir = 0; //1 左 2右
            if (vt.x > 0)
                dir = 1;
            else
                dir = 2;
            endDraghHandle?.Invoke(dir);
        }
    }

    protected override void LateUpdate()
    {
        base.LateUpdate();

        if (_isDragging || velocity != Vector2.zero)
        {
            return;
        }

        if (_delayDisableT >= 0)
        {
            _delayDisableT -= Time.deltaTime;
        }
        
        if (_delayDisableT < 0)
        {
            if (verticalScrollbar != null && verticalScrollbarVisibility == ScrollbarVisibility.AutoHide)
            {
                if (verticalScrollbar.gameObject.activeSelf)
                    verticalScrollbar.gameObject.SetActive(false);
            }

            if (horizontalScrollbar != null && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHide)
            {
                if (horizontalScrollbar.gameObject.activeSelf)
                    horizontalScrollbar.gameObject.SetActive(false);
            }
        }


    }

    private void Update()
    {
        if (refreshList.Count > 0)
        {
            var temp = refreshList[0];
            refreshList.RemoveAt(0);
            temp.Value?.Invoke();
        }
    }
}